
#define _FILE_OFFSET_BITS 64
#define FUSE_USE_VERSION 26

#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <osxfuse/fuse.h>

#include <syslog.h>

#include "InMemoryFileSystem.h"

InMemoryFileSystem g_memoryFileSystem;

static int
memory_fs_getattr(const char *path, struct stat *stbuf)
{
    syslog(LOG_NOTICE, "%s()", __FUNCTION__);
    
    bool got = g_memoryFileSystem.getAttributes(path, stbuf);
    if (!got)
        return -ENOENT;
    
    return 0;
}

static int
memory_fs_create(const char* fileName, mode_t mode, struct fuse_file_info* fileInfo)
{
    syslog(LOG_NOTICE, "%s()", __FUNCTION__);
    
    std::unique_ptr<InMemoryFile> file;
    bool created = g_memoryFileSystem.createFile(fileName, &file);
    if (!created)
        return -EACCES;
    
    fileInfo->fh = reinterpret_cast<uint64_t>(file.get());
    file.release();
    
    return 0;
}

static int
memory_fs_unlink(const char* fileName)
{
    syslog(LOG_NOTICE, "%s()", __FUNCTION__);
    
    g_memoryFileSystem.remove(fileName);
    
    return 0;
}

static int
memory_fs_open(const char* path, struct fuse_file_info* fileInfo)
{
    syslog(LOG_NOTICE, "%s()", __FUNCTION__);
    
    std::unique_ptr<InMemoryFile> file;
    bool created = g_memoryFileSystem.openFile(path, &file);
    if (!created)
        return -EACCES;
    
    fileInfo->fh = reinterpret_cast<uint64_t>(file.get());
    file.release();
    
    return 0;

}

static int
memory_fs_release(const char *path, struct fuse_file_info *fi)
{
    syslog(LOG_NOTICE, "%s()", __FUNCTION__);
    
    InMemoryFile* file = reinterpret_cast<InMemoryFile*>(fi->fh);
    delete file;
    
    return 0;
}

static int
memory_fs_read(const char *path, char *buf, size_t size, off_t offset,
               struct fuse_file_info* fileInfo)
{
    syslog(LOG_NOTICE, "%s()", __FUNCTION__);
    
    InMemoryFile* file = reinterpret_cast<InMemoryFile*>(fileInfo->fh);
    if (file)
    {
        size_t read = file->read(buf, size, offset);
        return static_cast<int>(read);
    }
    
    return 0;
}

static int
memory_fs_write(const char* path, const char* buf, size_t size, off_t offset, struct fuse_file_info* fileInfo)
{
    syslog(LOG_NOTICE, "%s()", __FUNCTION__);
    
    InMemoryFile* file = reinterpret_cast<InMemoryFile*>(fileInfo->fh);
    if (file)
    {
        size_t written = file->write(buf, size, offset);
        return static_cast<int>(written);
    }
    
    return 0;
}

static int
memory_fs_truncate(const char* path, off_t newSize)
{
    syslog(LOG_NOTICE, "%s()", __FUNCTION__);

    bool truncated = g_memoryFileSystem.truncateFile(path, newSize);
    if (!truncated)
        return -EACCES;
    
    return 0;
}


static int memory_fs_mkdir(const char* path, mode_t mode)
{
    syslog(LOG_NOTICE, "%s()", __FUNCTION__);
    
    bool created = g_memoryFileSystem.createDirectory(path, mode);
    if (!created)
        return -EACCES;
    
    return 0;
}

static int memory_fs_rmdir(const char* path)
{
    syslog(LOG_NOTICE, "%s()", __FUNCTION__);
    
    bool removed = g_memoryFileSystem.remove(path);
    if (!removed)
        return -EACCES;
    
    return 0;

}

static int memory_fs_rename(const char* path, const char* newPath)
{
    syslog(LOG_NOTICE, "%s()", __FUNCTION__);
    
    bool renamed = g_memoryFileSystem.renameDirectory(path, newPath);
    if (!renamed)
        return -EACCES;
    
    return 0;
    
}

static int
memory_fs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
              off_t offset, struct fuse_file_info *fi)
{
    syslog(LOG_NOTICE, "%s()", __FUNCTION__);
    
    std::vector<FileInfo> children;
    bool read = g_memoryFileSystem.readDirectory(path, &children);
    if (!read)
        return -ENOENT;
    
    for (auto child : children)
    {
        filler(buf, child.name.c_str(), NULL, 0);
    }
    
    return 0;
}

static struct fuse_operations memory_fs_filesystem_operations = {
    .getattr = memory_fs_getattr, /* To provide size, permissions, etc. */
    .create  = memory_fs_create,
    .unlink  = memory_fs_unlink,
    .open    = memory_fs_open,    /* To enforce read-only access.       */
    .read    = memory_fs_read,    /* To provide file content.           */
    .write   = memory_fs_write,
    .truncate = memory_fs_truncate,
    .release = memory_fs_release,
    .readdir = memory_fs_readdir, /* To provide directory listing.      */
    .mkdir   = memory_fs_mkdir,
    .rmdir   = memory_fs_rmdir,
    .rename  = memory_fs_rename
};

int
main(int argc, char **argv)
{
    return fuse_main(argc, argv, &memory_fs_filesystem_operations, NULL);
}
